package file

import (
	"errors"
	"net/http"
	"os"
	"strings"

	"chainmaker.org/chainmaker/smarteditor/common"
	"chainmaker.org/chainmaker/smarteditor/conf"
	"chainmaker.org/chainmaker/smarteditor/container"
	"chainmaker.org/chainmaker/smarteditor/contract"

	"chainmaker.org/chainmaker/smarteditor/event"
	"chainmaker.org/chainmaker/smarteditor/session"
	"chainmaker.org/chainmaker/smarteditor/util"

	"github.com/88250/gulu"
)

// fileType file type
type Type string

const (
	F Type = "f"
	D Type = "d"
)

func (t Type) ToString() string {
	return string(t)
}

// NewFileHandler handles request of creating file or directory.
func NewFileHandler(w http.ResponseWriter, r *http.Request) {
	uid, err := session.GetUidFromSession(r)
	if err != nil {
		log.Errorf("[NewFileHandler]. read uid form session fail. uid:%s, err: %v", uid, err)
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	args, err := util.ReadArgs(r.Body)
	if err != nil {
		log.Errorf("new file. decode args error. uid:%s, err: %v", uid, err)
		http.Error(w, "Forbidden", http.StatusBadRequest)
		return
	}

	path := args["path"].(string)
	fileTypeStr := args["fileType"].(string)
	sid := args["sid"].(string)
	var templateS string
	templateO, ok := args["template"]
	if ok {
		templateS, ok = templateO.(string)
	}
	templateSelector := getTemplateParam(templateS)
	if util.AnyEmpty(path, fileTypeStr, sid) {
		http.Error(w, "Forbidden", http.StatusBadRequest)
		return
	}
	if !session.CanAccess(uid, path) {
		http.Error(w, "Forbidden", http.StatusForbidden)
		return
	}

	result := gulu.Ret.NewResult()
	defer gulu.Ret.RetResult(w, r, result)
	wSession := session.WideSessions.Get(sid)
	if wSession == nil {
		log.Errorf("new file get sid %s found no wSession", sid)
		result.Code = -1
		result.Msg = sid + " get no wSession"
		return
	}
	log.Debugf("new file. created file start, user：%s, file type: %s, file: %s", wSession.UserID, fileTypeStr, path)
	err = createFile(uid, path, Type(fileTypeStr), templateSelector)
	log.Debugf("new file. created file success, user：%s, file type: %s, file: %s", wSession.UserID, fileTypeStr, path)
	if err != nil {
		wSession.EventQueue.Queue <- &event.Event{
			Code: event.EvtCodeServerInternalError,
			Sid:  sid,
			Data: "can't create file " + path}
		log.Debugf("new file. created file failed, user：%s, file type: %s, file: %s", wSession.UserID, fileTypeStr, path)
		result.Msg = err.Error()
		result.Code = -1
	}
	user := conf.GetUser(uid)
	if util.IsContractRootDirectory(user.Workspace, path) {
		contractName := strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(path, conf.GetSrcDir(user.Workspace)), "/"), "/")
		createErr := conf.CreateUserContractHidden(&conf.UserContractHidden{UserId: uid, Contract: contractName})
		if createErr != nil {
			log.Errorf("NewFileHandler CreateUserContractHidden uid %s,path %s,failed %s",
				uid, path, createErr.Error())
		}
	}
}

func GetContractTemplate(w http.ResponseWriter, r *http.Request) {
	result := gulu.Ret.NewResult()
	defer gulu.Ret.RetResult(w, r, result)
	result.Data = common.Templates
	result.Code = 0
	result.Msg = ""
}

func getTemplateParam(originTemplate string) string {
	templateSelector := common.TemplateDefault
	if strings.TrimSpace(originTemplate) == common.TemplateCMDFA1 {
		templateSelector = common.TemplateCMDFA1
	} else if strings.TrimSpace(originTemplate) == common.TemplateCMEVI1 {
		templateSelector = common.TemplateCMEVI1
	} else if strings.TrimSpace(originTemplate) == common.TemplateCMID1 {
		templateSelector = common.TemplateCMID1
	} else if strings.TrimSpace(originTemplate) == common.TemplateCMNFA1 {
		templateSelector = common.TemplateCMNFA1
	} else if strings.TrimSpace(originTemplate) == common.TemplateEVM {
		templateSelector = common.TemplateEVM
	}
	return templateSelector
}

// createFile creates file on the specified path.
func createFile(uid, path string, fileType Type, templateSelector string) error {
	if util.FileExists(path) {
		log.Errorf("create file. file has exists. path:%s, file type:%v", path, fileType)
		return errors.New("file has exists")
	}

	switch fileType {
	case F:
		if container.SC.ResourceManager.ReachFileCountLimit(uid, path) {
			log.Errorf("create file. reach file count limit. uid:%s, path:%s", uid, path)
			return errors.New("reach file count limit")
		}
		file, err := os.OpenFile(path, os.O_CREATE, 0775)
		if nil != err {
			log.Errorf("create file. open file fail. uid:%s, path:%s, err:%v", uid, path, err)
			return errors.New("server inner error")
		}
		defer util.CloseAndLogError(file)

		AddFileCount(uid, path)
		log.Debugf("created file [%s]", path)
		return nil
	case D:
		if contract.IsContractDirectory(uid, path) {
			if container.SC.ResourceManager.ReachContractCountLimit(uid) {
				log.Errorf("create file. reach contract count limit. uid:%s, path:%s", uid, path)
				return errors.New("reach contract count limit")
			}
		} else {
			if container.SC.ResourceManager.ReachFileCountLimit(uid, path) {
				log.Errorf("create file. reach file count limit. uid:%s, path:%s", uid, path)
				return errors.New("reach file count limit")
			}
		}
		err := os.Mkdir(path, 0775)
		if nil != err {
			log.Errorf("create file. make directory fail. uid:%s, path:%s, err:%v", uid, path, err)
			return errors.New("server inner error")
		}
		AddFileOrContractCount(uid, path)
		InitContractFile(uid, path, templateSelector)
		log.Debugf("created directory [%s]", path)
		return nil
	default:
		log.Errorf("unsupported file type [%s]", fileType)
		return errors.New("parameter error")
	}
}

func AddFileCount(uid, path string) {
	err := container.SC.ResourceManager.AddFile(uid, path, 1)
	if err != nil {
		log.Errorf("create file. increace contrcat count fail. uid:%s, path:%s, err:%v", uid, path, err)
	}
}

func AddFileOrContractCount(uid, path string) {
	if contract.IsContractDirectory(uid, path) {
		err := container.SC.ResourceManager.AddContract(uid, path, 1)
		if err != nil {
			log.Errorf("create file. increace contrcat count fail. uid:%s, path:%s, err:%v", uid, path, err)
		}
	} else {
		err := container.SC.ResourceManager.AddFile(uid, path, 1)
		if err != nil {
			log.Errorf("create file. increace contrcat count fail. uid:%s, path:%s, err:%v", uid, path, err)
		}
	}
}

// InitContractFile 创建文件夹的时候自动创建main.go 和 go.mod文件
func InitContractFile(uid, path, templateSelector string) error {
	if contract.IsContractDirectory(uid, path) {
		if templateSelector == common.TemplateEVM {
			index := strings.LastIndex(path, "/")
			name := path[index:] + ".sol"
			err := createFile(uid, path+name, Type("f"), templateSelector)
			if err != nil {
				return err
			}
		} else {
			mainContent := getMainFileContentByTemplateSelector(templateSelector)
			err := createFile(uid, path+"/main.go", Type("f"), templateSelector)
			if err != nil {
				return err
			}
			err = createFile(uid, path+"/go.mod", Type("f"), templateSelector)
			if err != nil {
				return err
			}
			if templateSelector == common.TemplateCMDFA1 {
				err = createFile(uid, path+"/dfa.go", Type("f"), templateSelector)
				if err != nil {
					return err
				}
				dfaFile, err := os.OpenFile(path+"/dfa.go", os.O_RDWR, 0666)
				if err != nil {
					return err
				}
				dfaFile.WriteString(common.ContentTemplateCMDFA1DFA)
			}
			mainFile, err := os.OpenFile(path+"/main.go", os.O_RDWR, 0666)
			if err != nil {
				return err
			}
			defer mainFile.Close()
			mainFile.WriteString(mainContent)
			// 写入main.go文件完毕
			gomodFile, err := os.OpenFile(path+"/go.mod", os.O_RDWR, 0666)
			if err != nil {
				return err
			}
			defer gomodFile.Close()
			dirName := strings.Split(path, "/")

			gomodFile.WriteString("module " + dirName[len(dirName)-1] + "\n\ngo 1.16\n\n")
			gomodFile.WriteString(common.TemplateGoModRequire)
		}
	}
	return nil
}

func getMainFileContentByTemplateSelector(templateSelector string) string {
	if templateSelector == common.TemplateCMDFA1 {
		return common.ContentTemplateCMDFA1Main
	} else if templateSelector == common.TemplateCMEVI1 {
		return common.ContentTemplateCMEVI1
	} else if templateSelector == common.TemplateCMID1 {
		return common.ContentTemplateCMID1
	} else if templateSelector == common.TemplateCMNFA1 {
		return common.ContentTemplateCMNFA1
	} else if templateSelector == common.TemplateEVM {
		return common.TemplateEVM
	}
	return common.ContentTemplateDefault
}
